﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LPLib
{
    /// <summary>
    /// Describes the three types of nucleotide mutation.
    /// </summary>
    public enum MutationType
    {
        /// <summary>
        /// Simple nucleotide point-substitution.
        /// </summary>
        Substitution,
        /// <summary>
        /// Insertion
        /// </summary>
        Insertion,
        /// <summary>
        /// Deletion
        /// </summary>
        Deletion
    };

    public enum SubstitutionType
    {
        Identity,
        Transition,
        TransversionToComplement,
        TransversionToNonComplement
    };

    /// <summary>
    /// Container for information about a nucleotide mutation.
    /// </summary>
    public class Mutation
    {
        /// <summary>
        /// The type of the mutation.
        /// </summary>
        public MutationType Type;
        /// <summary>
        /// The position of the mutation within the nucleotide sequence.
        /// </summary>
        public int Position;
        /// <summary>
        /// Further details about the mutation. The form of these details depend on the mutation type.
        /// </summary>
        public string Detail;

        public static string ToString(Mutation mutation, char delimiter = '\t')
        {
            return mutation.Type.ToString("g") + delimiter + mutation.Position.ToString("g") + delimiter + mutation.Detail;
        }

        public static Tuple<string, Mutation> FromString(string mutationString, char delimiter = '\t')
        {
            Mutation mutation = new Mutation();
            string[] parsed = mutationString.Split(delimiter);
            mutation.Type = (MutationType)Enum.Parse(typeof(MutationType), parsed[1]);
            mutation.Position = int.Parse(parsed[2]);
            StringBuilder sb = new StringBuilder(parsed[3]);
            for (int i = 4; i < parsed.Length; i++)
            {
                sb.Append(delimiter + parsed[i]);
            }
            mutation.Detail = sb.ToString();
            return new Tuple<string, Mutation>(parsed[0], mutation);
        }

        public override string ToString()
        {
            return Mutation.ToString(this);
        }
    }

    /// <summary>
    /// Encapsulates the methods required to analyze the mutations between the nucleotide sequences in an aligned PolymerPair.
    /// </summary>
    public static class MutationFinder
    {
        /// <summary>
        /// Returns a list of mutations identified in the specified PolymerPair.
        /// </summary>
        /// <param name="pair">An aligned Polymer pair to be examined for mutations.</param>
        /// <returns>A list of mutations.</returns>
        public static MutationList GetMutations(PolymerPair<byte, byte> pair, int readingFrame = 0, int lowerLimit = 0, int upperLimitSetBack = 0)
        {
            // Polymer0 is treated as the parental sequence
            MutationList ml = new MutationList();
            ml.Mutations = new List<Mutation>();
            bool deletion = false;
            bool insertion = false;
            string ins = string.Empty;

            int examinedLength = pair.Index.Length - upperLimitSetBack;
            ml.TotalBases = examinedLength;

            for (int i = lowerLimit; i < examinedLength; i++)
            {
                if (pair.Index[i][0] > -1 && pair.Polymer0.Seq[pair.Index[i][0]] != 16 && pair.Index[i][1] > -1 && pair.Polymer1.Seq[pair.Index[i][1]] != 16)
                {
                    if (!MixedNucleotideByte.AreConsistent(pair.Polymer0.Seq[pair.Index[i][0]], pair.Polymer1.Seq[pair.Index[i][1]]))
                    {
                        int aaPos = (int)Math.Floor((pair.Index[i][0] - readingFrame) / 3.0);
                        int codonStart = (int)(3 * Math.Floor((pair.Index[i][0] - readingFrame) / 3.0) + readingFrame);

                        string aa0;
                        if (codonStart + 2 < pair.Polymer0.Seq.Length && codonStart > -1)
                        {
                            string codon0 = codon0 = codon0 = new string(TypeConverter.ToChars(pair.Polymer0.Subsequence(codonStart, codonStart + 2)));
                            aa0 = Translator.Translate(codon0);
                        }
                        else
                        {
                            aa0 = "X";
                        }

                        string aa1;
                        if (codonStart + 2 < pair.Polymer1.Seq.Length && codonStart > -1)
                        {
                            string codon1 = new string(TypeConverter.ToChars(pair.Polymer1.Subsequence(codonStart, codonStart + 2)));
                            aa1 = Translator.Translate(codon1);
                        }
                        else
                        {
                            aa1 = "X";
                        }

                        ml.Mutations.Add(new Mutation()
                        {
                            Type = MutationType.Substitution,
                            Position = pair.Index[i][1],
                            Detail = TypeConverter.ToChar(pair.Polymer1.Seq[pair.Index[i][1]])
                            + "\t" + TypeConverter.ToChar(pair.Polymer0.Seq[pair.Index[i][0]]) +
                            "\t" + aaPos.ToString()
                            + "\t" + aa1
                            + "\t" + aa0
                        });
                    }
                }
                else
                {
                    if ((pair.Index[i][0] < 0 || pair.Polymer0.Seq[pair.Index[i][0]] == 16) && (pair.Index[i][1] > -1 && pair.Polymer1.Seq[pair.Index[i][1]] != 16))
                    {
                        // enter the deletion state
                        deletion = true;
                    }
                    else if ((pair.Index[i][0] > -1 && pair.Polymer0.Seq[pair.Index[i][0]] != 16) && (pair.Index[i][1] < 0 || pair.Polymer1.Seq[pair.Index[i][1]] == 16))
                    {
                        // enter the insertion state
                        insertion = true;
                        ins = TypeConverter.ToChar(pair.Polymer0.Seq[pair.Index[i][0]]).ToString();
                    }
                    for (int k = 1; i + k < pair.Index.Length; k++)
                    {
                        if (deletion)
                        {
                            if (pair.Index[i + k][0] > -1 && pair.Polymer0.Seq[pair.Index[i + k][0]] != 16)
                            {
                                // exit the deletion state
                                deletion = false;
                                ml.Mutations.Add(new Mutation()
                                {
                                    Type = MutationType.Deletion,
                                    Position = i,
                                    Detail = k.ToString()
                                });
                                i += k - 1;
                                break;
                            }
                        }
                        else if (insertion)
                        {
                            if ((pair.Index[i + k][1] < 0 || pair.Polymer1.Seq[pair.Index[i + k][1]] == 16) && (pair.Index[i][0] > -1 && pair.Polymer0.Seq[pair.Index[i][0]] != 16))
                            {
                                ins += TypeConverter.ToChar(pair.Polymer0.Seq[pair.Index[i + k][0]]);
                            }
                            else
                            {
                                insertion = false;
                                ml.Mutations.Add(new Mutation()
                                {
                                    Type = MutationType.Insertion,
                                    Position = i,
                                    Detail = ins.Length + "\t" + ins
                                });
                                i += k - 1;
                                break;
                            }
                        }
                    }
                }
            }
            Summarize(ml);
            return ml;
        }

        public static void GetMutations(Tree<Polymer<byte>> tree, Dictionary<string, MutationList> mutations)
        {
            foreach (Tree<Polymer<byte>> child in tree.Children)
            {
                GetMutations(child, mutations);
            }
            if (tree.Parent == null)
            {
                return;
            }

            PolymerPair<byte, byte> pair = new PolymerPair<byte, byte>(tree.Contents, tree.Parent.Contents);
            if (tree.Contents.Seq.Length != tree.Parent.Contents.Seq.Length)
            {
                throw new Exception("Sequences must be the same length");
            }
            int length = tree.Contents.Seq.Length;
            pair.Index = new int[tree.Contents.Seq.Length][];
            for (int i = 0; i < length; i++)
            {
                pair.Index[i] = new int[2] { i, i };
            }

            MutationList muList = GetMutations(pair);
            mutations.Add(tree.Contents.Name, muList);
        }

        /// <summary> 
        /// Produces a summary of the mutations passed in a list.
        /// </summary>
        /// <param name="ml">A mutation list. The list itself contains the summary upon return.</param>
        public static void Summarize(MutationList ml)
        {
            ml.TotalSubstitutions = 0;
            ml.NucleotidesDeleted = 0;
            ml.NucleotidesInserted = 0;
            foreach (Mutation m in ml.Mutations)
            {
                switch (m.Type)
                {
                    case MutationType.Substitution:
                        ml.TotalSubstitutions++;
                        break;
                    case MutationType.Insertion:
                        ml.NucleotidesInserted += int.Parse(m.Detail.Split('\t')[0]);
                        break;
                    case MutationType.Deletion:
                        ml.NucleotidesDeleted += int.Parse(m.Detail);
                        break;
                }
            }
        }

        /// <summary> 
        /// Produces a summary of the mutations passed in a list.
        /// </summary>
        /// <param name="ml">A mutation list. The list itself contains the summary upon return.</param>
        public static void CountTransitionsAndTransversion(MutationList ml)
        {
            foreach (Mutation m in ml.Mutations)
            {
                if (m.Type == MutationType.Substitution)
                {
                    string[] detail = m.Detail.Split('\t');
                    char alpha = detail[0][0];
                    char beta = detail[1][0];

                }
            }
        }
    }

    public static class MutationFinder<T,U>
    {
        public static double MutationFrequency(MonomerPair<T,U> mPair, PolymerPair<T, U> pPair, int lowerLimit = 0, int upperLimitSetBack = 0)
        {
            int nTotal = pPair.Index.Length - upperLimitSetBack;
            double nMutations = 0;
            for (int i = lowerLimit; i < nTotal; i++)
            {
                if (pPair.Index[i][0] > -1 && pPair.Index[i][1] > -1)
                {
                    nMutations += mPair.Mutation(pPair.Polymer0.Seq[pPair.Index[i][0]], pPair.Polymer1.Seq[pPair.Index[i][1]]);
                }
                else if (pPair.Index[i][0] < 0)
                {
                    nMutations += 1 - mPair.SecondIsGap(pPair.Polymer1.Seq[pPair.Index[i][1]]);
                }
                else
                {
                    nMutations += 1 - mPair.FirstIsGap(pPair.Polymer0.Seq[pPair.Index[i][0]]);
                }
            }
            return nMutations / (nTotal - lowerLimit);
        }


    }

    /// <summary>
    /// Encapsulates the result of a simple mutation analysis.
    /// </summary>
    public class MutationList
    {
        /// <summary>
        /// A list of mutations.
        /// </summary>
        public List<Mutation> Mutations;
        /// <summary>
        /// The total bases examined.
        /// </summary>
        public int TotalBases;
        /// <summary>
        /// The total number of nucleotide substitutions.
        /// </summary>
        public int TotalSubstitutions;
        /// <summary>
        /// The total number of nucleotides deleted.
        /// </summary>
        public int NucleotidesDeleted;
        /// <summary>
        /// The total number of nucleotides inserted.
        /// </summary>
        public int NucleotidesInserted;
        /// <summary>
        /// The header for saving the MutationList to file.
        /// </summary>
        public static string Header = "ReadID\tType\tPosition\tDetail.1\tDetail.2";

        public MutationList()
        {
            Mutations = new List<Mutation>();
        }

        public static string ToString(MutationList mutationList, char delimiter = '\t')
        {
            StringBuilder sb = new StringBuilder(MutationList.Header + "\n");
            foreach (Mutation m in mutationList.Mutations)
            {
                sb.Append(Mutation.ToString(m, delimiter) + "\n");
            }
            return sb.ToString();
        }

        public static string ToString(Dictionary<string, MutationList> mutationDict, char delimiter = '\t')
        {
            StringBuilder sb = new StringBuilder(MutationList.Header + "\n");
            foreach (KeyValuePair<string, MutationList> kvp in mutationDict)
            {
                foreach (Mutation m in kvp.Value.Mutations)
                {
                    sb.Append(kvp.Key + "\t" + Mutation.ToString(m, delimiter) + "\n");
                }
            }
            return sb.ToString();
        }

        public static MutationList FromString(string mutationListString)
        {
            MutationList ml = new MutationList();
            string[] lines = mutationListString.Split('\n');
            foreach (string line in lines)
            {
                if (line == MutationList.Header || line.Length == 0)
                    continue;
                Tuple<string, Mutation> tuple = Mutation.FromString(line.Trim());
                ml.Mutations.Add(tuple.Item2);
            }
            return ml;
        }

        public static Dictionary<string, MutationList> FromStringToDictionary(string mutationDictionaryString)
        {
            Dictionary<string, MutationList> mutationDictionary = new Dictionary<string, MutationList>();
            string[] lines = mutationDictionaryString.Split('\n');
            foreach (string line in lines)
            {
                if (line == MutationList.Header || line.Length == 0)
                    continue;

                Tuple<string, Mutation> tuple = Mutation.FromString(line.Trim());
                if (!mutationDictionary.ContainsKey(tuple.Item1))
                {
                    mutationDictionary.Add(tuple.Item1, new MutationList());
                }
                mutationDictionary[tuple.Item1].Mutations.Add(tuple.Item2);
            }

            return mutationDictionary;
        }
    }
}
